#include "Basic/GxLibBasic.h"
#include "Windows/GxWinForm.h"
#include "Windows/GxWindows.h"
#include "Math.h"
#include "../RayTracePro/RayTracePro.h"
#include "../RayTracePro/BitmapPrinter.h"
#include "../RayTracePro/RenderThreadStorage.h"
#include "../RayTracePro/FastRandom.h"
#include "../RayTracePro/GI.h"
#include "../RayTracePro/Debug.h"

#include "BackgroundShadingNode.h"

using namespace CpFinal;
//#define DEBUG_SINGLE_THREAD 1

using namespace GxLibBasic;
using namespace GxWinGUI;
using namespace GxLibMath;

using namespace RayTracePro;

const int WindowWidth = 800;
const int WindowHeight = 600;

#define WM_UPDATE_IMAGE WM_USER+101
#define BVH_PROFILE

class MainForm : public Form
{
protected:
	volatile bool completed;
	int GetEncoderClsid(const WCHAR* format, CLSID* pClsid)
	{
		UINT  num = 0;          // number of image encoders
		UINT  size = 0;         // size of the image encoder array in bytes

		ImageCodecInfo* pImageCodecInfo = NULL;

		GetImageEncodersSize(&num, &size);
		if(size == 0)
			return -1;  // Failure

		pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
		if(pImageCodecInfo == NULL)
			return -1;  // Failure

		GetImageEncoders(num, size, pImageCodecInfo);

		for(UINT j = 0; j < num; ++j)
		{
			if( wcscmp(pImageCodecInfo[j].MimeType, format) == 0 )
			{
				*pClsid = pImageCodecInfo[j].Clsid;
				free(pImageCodecInfo);
				return j;  // Success
			}    
		}

		free(pImageCodecInfo);
		return -1;  // Failure
	}
	bool ProcessMessage(WinMessage msg)
	{
		bool rs = Form::ProcessMessage(msg);
		if (msg.message == WM_UPDATE_IMAGE)
		{
			printer.Flush();
			Graphics(this->handle).DrawImage(printer.GetBitmap(), 0, 0);
			if (completed)
			{
				CLSID clsid;
				GetEncoderClsid(L"image/bmp", &clsid);
				printer.GetBitmap()->Save(L"D:\\RenderResult.bmp", &clsid);
				buffer->SavePFM(L"D:\\RenderResult.pfm");
			}
			return true;
		}
		else
		{
			return rs;
		}
	}
private:
	Vec3 viewCenter;
	float viewTheta, viewPhi, viewRadius;
	bool onAdjustView;
	int lastX, lastY;
	RefPtr<Scene> scene;
	RefPtr<Renderer> renderer;
	RefPtr<RenderBuffer> buffer;
	RefPtr<RenderThreadScheduler> scheduler;
	RayTraceSystem * system;
	BitmapPrinter printer;
	LARGE_INTEGER startTime, lastUpdateTime;
	double invFreq;
	int threads;
	static ShadingNode * createBackgroundShader()
	{
		return new BackgroundShadingNode();
	}
	void InitRenderer()
	{
		system = CreateRayTraceSystem();
		system->GetShaderRegistry()->RegisterShader(L"BackgroundShader", createBackgroundShader);
		buffer = new RenderBuffer();
		buffer->SetSize(WindowWidth, WindowHeight);
		renderer = system->CreateRenderer();
		renderer->SetPerspective(WindowWidth, WindowHeight, 50.0f);
		renderer->SetScreenBuffer(buffer.operator->());
		printer.SetResolution(WindowWidth, WindowHeight);
		for (int i = 0; i<WindowWidth; i++)
			for (int j = 0; j<WindowHeight; j++)
				printer.SetPixel(i, j, RayTracePro::Color(0,0,0));
		LARGE_INTEGER freq;
		QueryPerformanceFrequency(&freq);
		invFreq = 1.0 / (double)freq.QuadPart;

		viewTheta = viewPhi = 0.0f;
		viewRadius = 30.0f;
		viewCenter.SetZero();

#ifndef DEBUG_SINGLE_THREAD
		threads = DetectCpuCount();
		if (threads == 1) threads = 2;
#else
		threads = 1;
#endif
	}

	void UpdateViewTransform()
	{
		Matrix4 trans;
		Matrix4::CreateIdentityMatrix(trans);
		Matrix4::Translation(trans, viewCenter.x, viewCenter.y, viewCenter.z);
		/*Matrix4::RotationY(trans, viewTheta);
		Matrix4::RotationX(trans2, viewPhi);
		trans *= trans2;
		Vec3 dir(0.0f, 0.0f, -1.0f);
		Vec3 transDir;
		trans.Transform(transDir, dir);
		Vec3 pos = viewCenter;
		Vec3 dpos;
		Vec3::Scale(dpos, transDir, -1.0f * viewRadius);
		pos += dpos;
		Matrix4::Translation(trans2, pos.x, pos.y, pos.z);
		trans.LeftMultiply(trans2);*/
		renderer->Camera.CameraTransform = trans;
	}

	MeshGeometry * LoadBackgroundMesh(const wchar_t * fileName)
	{
		MeshGeometry * mesh = new MeshGeometry();

		GeometryLoader::Init();
		GeometryLoader::LoadGeometryFromFile(system, *mesh, fileName);
		GeometryLoader::Finalize();
		for (int i = 0; i<mesh->Materials.Count(); i++)
		{
			ShadingNodeParameter param;
			mesh->Materials[i]->Shader->GetParameter(L"diffuse", param);
			auto * texshader = param.GetShader();
			texshader->AddRef();
			texshader->AddRef();
			mesh->Materials[i]->Shader->Free();
			mesh->Materials[i]->Shader = system->CreateShader(L"BackgroundShader");
			mesh->Materials[i]->Shader->SetParameter(L"diffuse", texshader);
			mesh->Materials[i]->GI.Environment = true;
			mesh->Materials[i]->GI.Lambert = false;
			mesh->Materials[i]->GI.Diffuse.Set(texshader);
		}
		mesh->CastShadow = false;
		mesh->AutoFlipNormals = true;
		mesh->UpdateAccelerateStructure(system);
		return mesh;
	}

	void InitScene0()
	{
		//viewTheta = -3.473206f; viewPhi = 0.785398f;
		viewRadius = 1.0f;
		viewTheta = 0.0f; 
		viewPhi = 0.0f;
		viewCenter.SetZero();
		viewCenter.y = 1.6f;
		scene = system->CreateScene();
		//scene->Environment = new EnvironmentShader(system, L"../Media/sky_midnoon.jpg");
		system->EnableGI = true;
		system->TotalPhotons = 1;
		system->PhotonTraceDepth = 30;
		system->PhotonsPerEstimate = 250;
		system->FinalGatherRays = 240;
		system->EnableFinalGather = true;
		scene->GlobalPhotonMapRadianceModifier.Value = 80.0f;
		scene->GlobalPhotonMapRadianceModifier.Modifier = PhotonMapRadiusModifier::Set;
		Material * stdMaterial = scene->CreateMaterial();
		renderer->SetMultiSample(8);
		stdMaterial->Shader = system->CreateShader(L"Lambert");
		stdMaterial->Shader->SetParameter(L"Diffuse", Vec4(0.6f, 0.6f, 0.5f, 1.0f));
		stdMaterial->GI.Diffuse.Set(Vec4(1.0f, 1.0f, 1.0f, 1.0f));
		stdMaterial->GI.Lambert = true;
		system->ShadowBias = 0.02f;

		Material * reflectMaterial = scene->CreateMaterial();
		reflectMaterial->Shader = system->CreateShader(L"Fresnel");
		reflectMaterial->Shader->SetParameter(L"RefractionIndex", Vec4(1.4f));
		/*for (int i = 0; i<8; i++)
			for (int j = 0; j<8; j++)
			{
				Light * light = scene->CreateLight();
				light->CastShadow = true;
				light->IsDirectional = true;
				light->Intensity = Vec3(0.0013f, 0.0013f, 0.0013f);
				light->Direction = Vec3(-2.0f, -0.3f + (i-4)*0.3f , -1.0f+ (j-4)*0.5f);
				Vec3::Normalize(light->Direction, light->Direction);
				light->Position = Vec3(0.0f, 600.0f, 0.0f);
			}*/
		/*for (int i = 0; i<8; i++)
			for (int j = 0; j<8; j++)
			{
				Light * light = scene->CreateLight();
				light->CastShadow = true;
				light->IsDirectional = true;
				light->Intensity = Vec3(0.001f, 0.001f, 0.001f);
				light->Direction = Vec3(2.0f, -0.5f + (i-4)*0.5f , -1.0f+ (j-4)*0.5f);
				Vec3::Normalize(light->Direction, light->Direction);
				light->Position = Vec3(0.0f, 600.0f, 0.0f);
			}*/
		system->AmbientLight = Vec3(0.001f, 0.001f, 0.001f);
		MeshGeometry * mesh = LoadBackgroundMesh(L"../Media/cp_final/1/result.obj");
		scene->AddGeometry(mesh);

		for (int i = 0; i<1; i++)
		{
			mesh = new MeshGeometry();
			Matrix4 trans, scale;
			Matrix4::Translation(trans, 0.1f, -0.23f, -5.0f-i*4.0f);
			Matrix4::Scale(scale, 1.0f/15.0f, 1/15.0f, 1/15.0f);
			Matrix4::MultiplyFPU(trans, trans, scale);
			mesh->SetTransformation(trans);
			GeometryLoader::Init();
			GeometryLoader::LoadGeometryFromFile(system, *mesh, L"../Media/bunny.obj");
			GeometryLoader::Finalize();
			mesh->Material = stdMaterial;
			mesh->UpdateAccelerateStructure(system);

			scene->AddGeometry(mesh);
		}
		

		UpdateViewTransform();
	}

	void InitScene1()
	{
		//viewTheta = -3.473206f; viewPhi = 0.785398f;
		viewRadius = 1.0f;
		viewTheta = 0.0f; 
		viewPhi = 0.0f;
		viewCenter.SetZero();
		viewCenter.y = 1.6f;
		scene = system->CreateScene();
		//scene->Environment = new EnvironmentShader(system, L"../Media/sky_midnoon.jpg");
		system->EnableGI = true;
		system->TotalPhotons = 1;
		system->PhotonTraceDepth = 30;
		system->PhotonsPerEstimate = 250;
		system->FinalGatherRays = 240;
		system->EnableFinalGather = true;
		renderer->SetPerspective(WindowWidth, WindowHeight, 65.0f);
		scene->GlobalPhotonMapRadianceModifier.Value = 80.0f;
		scene->GlobalPhotonMapRadianceModifier.Modifier = PhotonMapRadiusModifier::Set;
		Material * stdMaterial = scene->CreateMaterial();
		renderer->SetMultiSample(8);
		stdMaterial->Shader = system->CreateShader(L"Lambert");
		stdMaterial->Shader->SetParameter(L"Diffuse", Vec4(0.6f, 0.6f, 0.5f, 1.0f));
		stdMaterial->GI.Diffuse.Set(Vec4(1.0f, 1.0f, 1.0f, 1.0f));
		stdMaterial->GI.Lambert = true;
		system->ShadowBias = 0.02f;

		Material * reflectMaterial = scene->CreateMaterial();
		reflectMaterial->Shader = system->CreateShader(L"Fresnel");
		reflectMaterial->Shader->SetParameter(L"RefractionIndex", Vec4(1.4f));
		for (int i = 0; i<10; i++)
			for (int j = 0; j<10; j++)
			{
				Light * light = scene->CreateLight();
				light->CastShadow = true;
				light->IsDirectional = true;
				light->Intensity = Vec3(0.0016f, 0.0016f, 0.0016f);
				light->Direction = Vec3(-2.0f, -0.8f + (i-4)*0.2f , -1.0f+ (j-4)*0.2f);
				Vec3::Normalize(light->Direction, light->Direction);
				light->Position = Vec3(0.0f, 600.0f, 0.0f);
			}
		for (int i = 0; i<8; i++)
			for (int j = 0; j<8; j++)
			{
				Light * light = scene->CreateLight();
				light->CastShadow = true;
				light->IsDirectional = true;
				light->Intensity = Vec3(0.0008f, 0.0008f, 0.0008f);
				light->Direction = Vec3(2.0f, -0.1f + (i-4)*0.5f , -1.0f+ (j-4)*0.5f);
				Vec3::Normalize(light->Direction, light->Direction);
				light->Position = Vec3(0.0f, 600.0f, 0.0f);
			}
		system->AmbientLight = Vec3(0.003f, 0.003f, 0.003f);
		MeshGeometry * mesh = LoadBackgroundMesh(L"../Media/cp_final/2/result.obj");
		scene->AddGeometry(mesh);

		/*for (int i = 0; i<1; i++)
		{
			mesh = new MeshGeometry();
			Matrix4 trans, scale;
			Matrix4::Translation(trans, -0.1f, 0.0f, -3.5f);
			Matrix4::Scale(scale, 1.0f/40.0f, 1/40.0f, 1/40.0f);
			Matrix4::MultiplyFPU(trans, trans, scale);
			mesh->SetTransformation(trans);
			GeometryLoader::Init();
			GeometryLoader::LoadGeometryFromFile(system, *mesh, L"../Media/zombie.obj");
			GeometryLoader::Finalize();
			mesh->Material = stdMaterial;
			mesh->UpdateAccelerateStructure(system);

			scene->AddGeometry(mesh);
		}*/
		

		UpdateViewTransform();
	}

	void InitScene2()
	{
		scene = system->CreateScene();
		scene->Environment = new EnvironmentShader(system, L"../Media/DH046LL.jpg");
		Material * stdMaterial = scene->CreateMaterial();
		stdMaterial->Shader = system->CreateShader(L"Lambert");
		stdMaterial->GI.Lambert = true;
		stdMaterial->GI.Diffuse.Set(stdMaterial->Shader);
		stdMaterial->Shader->AddRef();
		stdMaterial->Shader->SetParameter(L"Diffuse", Vec4(1.0f, 1.0f, 1.0f, 1.0f));
		//reflectMaterial->Shader = system->CreateShader(L"Fresnel");
		//reflectMaterial->Shader->SetParameter(L"RefractionIndex", Vec4(1.4f));

		Light * light = scene->CreateLight();
		light->CastShadow = false;
		light->Position = Vec3(300.0f, 600.0f, 300.0f);

		MeshGeometry * mesh = new MeshGeometry();
		Matrix4 trans, rot;
		Matrix4::Translation(trans, 0.0f, -8.0f, 0.0f);
		mesh->SetTransformation(trans);

		GeometryLoader::Init();
		GeometryLoader::LoadGeometryFromFile(system, *mesh, L"../Media/bunny.obj");
		GeometryLoader::Finalize();
		mesh->Material = stdMaterial;
		mesh->UpdateAccelerateStructure(system);

		scene->AddGeometry(mesh);

		viewRadius = 15.0f;
		viewTheta = -0.494533f; 
		viewPhi = 0.623599f;
		viewCenter.SetZero();
		viewCenter.x-=3.0f;
		UpdateViewTransform();
	}

	void InitScene3()
	{
		scene = system->CreateScene();
		scene->Environment = new EnvironmentShader(system, L"../Media/DH046LL.jpg");
		Material * stdMaterial = scene->CreateMaterial();
		stdMaterial->Shader = system->CreateShader(L"Lambert");
		stdMaterial->Shader->SetParameter(L"Diffuse", Vec4(0.72f, 0.62f, 0.52f, 1.0f));
		stdMaterial->GI.Diffuse.Set(Vec4(0.72f, 0.62f, 0.52f, 1.0f));
		stdMaterial->GI.Lambert = true;
		Material * groundMaterial = scene->CreateMaterial();
		groundMaterial->Shader = system->CreateShader(L"Lambert");
		groundMaterial->Shader->SetParameter(L"Diffuse", Vec4(0.6f, 0.6f, 0.6f, 1.0f));
		groundMaterial->GI.Diffuse.Set(Vec4(0.6f, 0.6f, 0.6f, 1.0f));
		groundMaterial->GI.Lambert = true;
		Light * light = scene->CreateLight();
		light->CastShadow = true;
		light->IsDirectional = true;
		
		light->Direction = Vec3(-3.0f, -8.0f, -6.0f);
		Vec3::Normalize(light->Direction, light->Direction);
		light->Position = Vec3(300.0f, 600.0f, 300.0f);

		system->AmbientLight.SetZero();
		system->EnableFinalGather = false;
		system->EnableGI = false;
		system->TotalPhotons = 500000;
		renderer->SetMultiSample(8);
		MeshGeometry * mesh = new MeshGeometry();
		LoadSponge(*mesh, 4);
		mesh->Material = stdMaterial;
		mesh->UpdateAccelerateStructure(system);
		
		scene->AddGeometry(mesh);

		{
			MeshGeometry * mesh = new MeshGeometry();
			mesh->Material = groundMaterial;
			mesh->Normals.Add(Vec3(0.0f, 1.0f, 0.0f));
			mesh->TexCoords.Add(Vec2(0.0f, 0.0f));
			mesh->TexCoords.Add(Vec2(0.0f, 100.0f));
			mesh->TexCoords.Add(Vec2(100.0f, 100.0f));
			mesh->TexCoords.Add(Vec2(100.0f, 0.0f));
			Vec3 verts[3];
			verts[0] = Vec3(-10.0f, -1.0f, -10.0f);
			verts[1] = Vec3(-10.0f, -1.0f, 10.0f);
			verts[2] = Vec3(10.0f, -1.0f, 10.0f);
			int norms[3] = {0,0,0};
			int texs[3] = {0, 1, 2};
			mesh->AddFace(verts, norms, texs);
			verts[0] = Vec3(-10.0f, -1.0f, -10.0f);
			verts[1] = Vec3(10.0f, -1.0f, 10.0f);
			verts[2] = Vec3(10.0f, -1.0f, -10.0f);
			int texs2[3] = {0, 2, 3};
			mesh->AddFace(verts, norms, texs2);
			mesh->UpdateAccelerateStructure(system);
			scene->AddGeometry(mesh);
		}

		viewRadius = 2.0f;
		viewTheta = 0.610865f;
		viewPhi = 0.353972f;
		viewCenter.SetZero();
		viewCenter.y = -0.2f;
		UpdateViewTransform();
	}

	void InitScene4()
	{
		scene = system->CreateScene();
		scene->Environment = new EnvironmentShader(system, L"../Media/DH046LL.jpg");
		Material * stdMaterial = scene->CreateMaterial();
		stdMaterial->Shader = system->CreateShader(L"Lambert");
		stdMaterial->GI.Lambert = true;
		stdMaterial->GI.Diffuse.Set(stdMaterial->Shader);
		stdMaterial->Shader->AddRef();
		stdMaterial->Shader->SetParameter(L"Diffuse", Vec4(1.0f, 1.0f, 1.0f, 1.0f));
		//reflectMaterial->Shader = system->CreateShader(L"Fresnel");
		//reflectMaterial->Shader->SetParameter(L"RefractionIndex", Vec4(1.4f));

		Light * light = scene->CreateLight();
		light->CastShadow = false;
		light->Position = Vec3(300.0f, 600.0f, 300.0f);

		MeshGeometry * mesh = new MeshGeometry();
		Matrix4 trans, rot;
		Matrix4::Translation(trans, 0.0f, -8.0f, 0.0f);
		mesh->SetTransformation(trans);

		GeometryLoader::Init();
		GeometryLoader::LoadGeometryFromFile(system, *mesh, L"../Media/dragon.obj");
		GeometryLoader::Finalize();
		mesh->Material = stdMaterial;
		mesh->UpdateAccelerateStructure(system);

		scene->AddGeometry(mesh);

		viewRadius = 15.0f;
		viewTheta = -0.494533f; 
		viewPhi = 0.623599f;
		viewCenter.SetZero();
		viewCenter.y = -8.1f;
		UpdateViewTransform();
	}

	void InitScene5()
	{
		scene = system->CreateScene();
		scene->Environment = new EnvironmentShader(system, L"../Media/DH046LL.jpg");
		Material * stdMaterial = scene->CreateMaterial();
		stdMaterial->Shader = system->CreateShader(L"Lambert");
		stdMaterial->GI.Lambert = true;
		stdMaterial->GI.Diffuse.Set(stdMaterial->Shader);
		stdMaterial->Shader->AddRef();
		stdMaterial->Shader->SetParameter(L"Diffuse", Vec4(1.0f, 1.0f, 1.0f, 1.0f));
		//reflectMaterial->Shader = system->CreateShader(L"Fresnel");
		//reflectMaterial->Shader->SetParameter(L"RefractionIndex", Vec4(1.4f));

		Light * light = scene->CreateLight();
		light->CastShadow = false;
		light->Position = Vec3(300.0f, 600.0f, 300.0f);

		MeshGeometry * mesh = new MeshGeometry();
		Matrix4 trans, rot;
		Matrix4::Translation(trans, 0.0f, -8.0f, 0.0f);
		mesh->SetTransformation(trans);

		GeometryLoader::Init();
		GeometryLoader::LoadGeometryFromFile(system, *mesh, L"../Media/budha.obj");
		GeometryLoader::Finalize();
		mesh->Material = stdMaterial;
		mesh->UpdateAccelerateStructure(system);

		scene->AddGeometry(mesh);

		viewRadius = 15.0f;
		viewTheta = -0.494533f; 
		viewPhi = 0.623599f;
		viewCenter.SetZero();
		viewCenter.y = -8.1f;
		UpdateViewTransform();
	}

	void InitScene7()
	{
		scene = system->CreateScene();
		scene->Environment = new EnvironmentShader(system, L"../Media/DH046LL.jpg");
		Material * stdMaterial = scene->CreateMaterial();
		stdMaterial->Shader = system->CreateShader(L"Lambert");
		stdMaterial->GI.Lambert = true;
		stdMaterial->GI.Diffuse.Set(stdMaterial->Shader);
		stdMaterial->Shader->AddRef();
		stdMaterial->Shader->SetParameter(L"Diffuse", Vec4(1.0f, 1.0f, 1.0f, 1.0f));
		//reflectMaterial->Shader = system->CreateShader(L"Fresnel");
		//reflectMaterial->Shader->SetParameter(L"RefractionIndex", Vec4(1.4f));

		Light * light = scene->CreateLight();
		light->CastShadow = false;
		light->Position = Vec3(300.0f, 600.0f, 300.0f);

		MeshGeometry * mesh = new MeshGeometry();
		Matrix4 trans, rot;
		Matrix4::Translation(trans, 0.0f, -0.0f, 0.0f);
		mesh->SetTransformation(trans);

		GeometryLoader::Init();
		GeometryLoader::LoadGeometryFromFile(system, *mesh, L"../Media/san-miguel.obj");
		GeometryLoader::Finalize();
		mesh->Material = stdMaterial;
		mesh->UpdateAccelerateStructure(system);

		scene->AddGeometry(mesh);

		viewRadius = 15.0f;
		viewTheta = 0.0f; 
		viewPhi = 0.0;
		viewCenter.SetZero();
		UpdateViewTransform();
	}

	void InitScene6()
	{
		scene = system->CreateScene();
		scene->Environment = new EnvironmentShader(system, L"../Media/DH046LL.jpg");
		Material * stdMaterial = scene->CreateMaterial();
		stdMaterial->Shader = system->CreateShader(L"Lambert");
		stdMaterial->GI.Lambert = true;
		stdMaterial->GI.Diffuse.Set(stdMaterial->Shader);
		stdMaterial->Shader->AddRef();
		stdMaterial->Shader->SetParameter(L"Diffuse", Vec4(1.0f, 1.0f, 1.0f, 1.0f));
		//reflectMaterial->Shader = system->CreateShader(L"Fresnel");
		//reflectMaterial->Shader->SetParameter(L"RefractionIndex", Vec4(1.4f));

		Light * light = scene->CreateLight();
		light->CastShadow = false;
		light->Position = Vec3(300.0f, 600.0f, 300.0f);

		MeshGeometry * mesh = new MeshGeometry();
		Matrix4 trans, rot;
		Matrix4::Translation(trans, 0.0f, -0.0f, 0.0f);
		mesh->SetTransformation(trans);

		GeometryLoader::Init();
		GeometryLoader::LoadGeometryFromFile(system, *mesh, L"../Media/plane.obj");
		GeometryLoader::Finalize();
		mesh->Material = stdMaterial;
		mesh->UpdateAccelerateStructure(system);

		scene->AddGeometry(mesh);

		viewRadius = 15.0f;
		viewTheta = 0.0f; 
		viewPhi = 0.0;
		viewCenter.SetZero();
		UpdateViewTransform();
	}

	void MainForm_Paint(Object * sender, PaintEventArgs args)
	{
		//args.Graphics->Clear(Gdiplus::Color(0, 0, 0));
		args.Graphics->DrawImage(printer.GetBitmap(), 0, 0);
	}

	void TestPixel(int x, int y)
	{
		if (scene)
		{
			RenderThreadStorage storage(50);
			TlsSetValue(system->TlsId, &storage);
			DifferentialGeometry dgeom;
			Vec4 rs; rs.SetZero();
			system->DebugPrint = true;
			renderer->RenderPixel(rs, dgeom, x, y);
			printf("Debug render pixel (%d, %d) = (%.3f, %.3f, %.3f, %.3f)\n", x, y, rs.x, rs.y, rs.z, rs.w);
		
			printf("Intersect(%d): \n  normal(%f, %f, %f), pos(%f, %f, %f), tex(%f, %f)\n", dgeom.Geometry, dgeom.Normal.x, dgeom.Normal.y, dgeom.Normal.z,
				dgeom.Position.x, dgeom.Position.y, dgeom.Position.z, dgeom.UV.x, dgeom.UV.y);
			system->DebugPrint = false;
			rs = buffer->GetPixel(x, y);
			printf("buffer (%d, %d) = (%.3f, %.3f, %.3f, %.3f)\n", x, y, rs.x, rs.y, rs.z, rs.w);

		}
	}

	void MainForm_MouseDown(Object * sender, MouseEventArgs args)
	{
		if (renderer)
		{
			if (args.Button == GxWinGUI::mbRight)
			{
				TestPixel(args.X, args.Y);
			}
			else
			{
				onAdjustView = true;
				lastX = args.X;
				lastY = args.Y;
				SetCapture(this->GetHandle());
			}
		}
	}

	void MainForm_MouseMove(Object * sender, MouseEventArgs args)
	{
		if (onAdjustView)
		{
			viewPhi += (args.Y-lastY)/180.0f*PI;
			viewTheta += (lastX - args.X)/180.0f*PI;
			viewPhi = min(viewPhi, PI/2);
			viewPhi = max(viewPhi, -PI/2);
			lastX = args.X;
			lastY = args.Y;
			if (scheduler)
			{
				system->BvhProfiler.Clear();
				UpdateViewTransform();
				//memset(buffer->Buffer.Buffer(), 0, sizeof(Vec4)*buffer->Buffer.Count());
				QueryPerformanceCounter(&startTime);
				lastUpdateTime = startTime;
				scheduler->Render(threads);
				printf("ViewTheta = %f; ViewPhi = %f\n", viewTheta, viewPhi);
			}
		}
	}

	void MainForm_MouseUp(Object * sender, MouseEventArgs args)
	{
		onAdjustView = false;
		ReleaseCapture();
	}

	void MainForm_KeyPressed(Object * sender, KeyEventArgs args)
	{
		bool render = false;
		if (args.Key == L'0')
		{
			InitScene0();
			render = true;
		}
		else if (args.Key == L'1')
		{
			InitScene1();
			render = true;
		}
		else if (args.Key == L'2')
		{
			InitScene2();
			render = true;
		}
		else if (args.Key == L'3')
		{
			InitScene3();
			render = true;
		}
		else if (args.Key == L'4')
		{
			InitScene4();
			render = true;
		}
		else if (args.Key == L'5')
		{
			InitScene5();
			render = true;
		}
		else if (args.Key == L'6')
		{
			InitScene6();
			render = true;
		}
		else if (args.Key == L'7')
		{
			InitScene7();
			render = true;
		}
		else if (args.Key == VK_ESCAPE)
		{
			if (scheduler)
				scheduler->Cancel();
		}
		else if (args.Key == L'D' || args.Key == L'd')
		{
			TestPixel(264, 168);
		}
		else if (args.Key == L'X' || args.Key == L'x')
		{
			if (renderer)
			{
				renderer->DebugDrawPhotons();
				RefreshContent();
			}
		}
		if (render)
		{
			StartRender();
		}
	}

	void StartRender()
	{
		if (scheduler)
			scheduler->Cancel();
		system->BvhProfiler.Clear();
		completed = false;
		scheduler = new RenderThreadScheduler(renderer.operator->(), 64, 64);
		scheduler->RenderCompleted.Bind(this, &MainForm::RenderCompleted);
		scheduler->BlockCompleted.Bind(this, &MainForm::BlockCompleted);
		scheduler->BlockAssigned.Bind(this, &MainForm::BlockAssigned);
		renderer->SetScene(scene.operator->());
		scene->PrepareRendering(10);
		printf("Render started...\n");
		QueryPerformanceCounter(&startTime);
		lastUpdateTime = startTime;
		scheduler->Render(threads);
	}

	void RefreshContent()
	{
		for (int i = 0; i < buffer->Height; i++)
		{
			for (int j = 0; j < buffer->Width; j++)
			{
				RayTracePro::Color c;
				Vec4 & pix = buffer->GetPixel(j, i);
				c.R = min(255, Round(pix.x * 255));
				c.G = min(255, Round(pix.y * 255));
				c.B = min(255, Round(pix.z * 255));
				c.A = 255;
				printer.SetPixel(j, i, c);
			}
		}
		printer.Flush();
		Graphics(this->handle).DrawImage(printer.GetBitmap(), 0, 0);
	}

	void BlockCompleted(const RenderTask& task)
	{
		for (int i = task.y; i < task.h + task.y; i++)
		{
			for (int j = task.x; j < task.w + task.x; j++)
			{
				RayTracePro::Color c;
				Vec4 & pix = buffer->GetPixel(j, i);
				c.R = min(255, Round(pix.x * 255));
				c.G = min(255, Round(pix.y * 255));
				c.B = min(255, Round(pix.z * 255));
				c.A = 255;
				printer.SetPixel(j, i, c);
			}
		}
	}

	void BlockAssigned(const RenderTask& task)
	{
		printer.DrawFocusRect(task.x, task.x + task.w - 1, task.y, task.y + task.h - 1);
		LARGE_INTEGER endTime;
		QueryPerformanceCounter(&endTime);
		if ((endTime.QuadPart - lastUpdateTime.QuadPart) * invFreq > 1.0)
		{
			PostMessage(this->handle, WM_UPDATE_IMAGE, 0, 0);
			lastUpdateTime = endTime;
		}
	}

	HANDLE renderEvent;

	void RenderCompleted(bool success)
	{
		LARGE_INTEGER endTime;
		QueryPerformanceCounter(&endTime);
		
		double time = (endTime.QuadPart-startTime.QuadPart) * invFreq;
		if (success)
			printf("Render completed.\n");
		else
			printf("Render cancelled.\n");
		printf("Time elapsed: %.2fs\n", time);
		completed = true;

		printf("KD Traversal time: %fs\n", DebugTimming::GetTime(0));
		printf("   Primary triangle time: %fs\n", DebugTimming::GetTime(1));
		printf("   Shadow triangle time: %fs\n", DebugTimming::GetTime(2));

#ifdef BVH_PROFILE
		if (system->BvhProfiler.rays == 0)
			system->BvhProfiler.rays = 1;
		printf("%d rays tested.\n", system->BvhProfiler.rays);
		printf("Node Traversed: avg. %f, min. %d, max. %d\n", (float)(system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), (int)(system->BvhProfiler.minNodes), (int)(system->BvhProfiler.maxNodes));
		printf("Leaves Traversed: avg. %f, min. %d, max. %d\n", (float)(system->BvhProfiler.sumLeaves/(double)system->BvhProfiler.rays), (int)(system->BvhProfiler.minLeaves), (int)(system->BvhProfiler.maxLeaves));
		printf("Triangles Tested: avg. %f, min. %d, max. %d\n", (float)(system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays), (int)(system->BvhProfiler.minTri), (int)(system->BvhProfiler.maxTri));
		printf("Triangles Hit: avg. %f, min. %d, max. %d\n", (float)(system->BvhProfiler.sumHit/(double)system->BvhProfiler.rays), (int)(system->BvhProfiler.minHit), (int)(system->BvhProfiler.maxHit));
		printf("Triangles Hit Rate: avg. %f, min. %f, max. %f\n", system->BvhProfiler.sumRate/system->BvhProfiler.avgRateCount, system->BvhProfiler.minRate, system->BvhProfiler.maxRate);
#endif
		PostMessage(this->handle, WM_UPDATE_IMAGE, 0, 0);
		SetEvent(renderEvent);
	}

	void RunBenchmark()
	{
		renderEvent = CreateEvent(NULL, false, false, NULL);
		FILE * file;
		fopen_s(&file, "bvh_benchmark.txt", "wt");
		
		for (int z = 0; z<=1; z++)
		{
			char * sahMode;
			if (z == 0) sahMode = "plain"; else sahMode = "layered";
			
			if (z == 0)
				system->PlainSAH = true;
			else
				system->PlainSAH = false;
			fprintf(file, "plane_%s\n", sahMode);
			for (float f = 0.45f; f<1.05f; f+=0.05f)
			{
				system->SahFactor = f;
				InitScene6();
				StartRender();
				::WaitForSingleObject(renderEvent, INFINITE);
				printf("%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
				fprintf(file, "%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
			}
			/*
			fprintf(file, "budha_%s\n", sahMode);
			for (float f = 0.45f; f<1.05f; f+=0.05f)
			{
				system->SahFactor = f;
				InitScene5();
				StartRender();
				::WaitForSingleObject(renderEvent, INFINITE);
				printf("%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
				fprintf(file, "%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
			}
			
			fprintf(file, "dragon_%s\n", sahMode);
			for (float f = 0.45f; f<1.05f; f+=0.05f)
			{
				system->SahFactor = f;
				InitScene4();
				StartRender();
				::WaitForSingleObject(renderEvent, INFINITE);
				printf("%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
				fprintf(file, "%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
			}
			
			fprintf(file, "sponza_%s\n", sahMode);
			for (float f = 0.45f; f<1.05f; f+=0.05f)
			{
				system->SahFactor = f;
				InitScene0();
				StartRender();
				::WaitForSingleObject(renderEvent, INFINITE);
				printf("%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
				fprintf(file, "%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
			}
			fprintf(file, "conference_%s\n", sahMode);
			for (float f = 0.70f; f<1.05f; f+=0.05f)
			{
				system->SahFactor = f;
				InitScene1();
				StartRender();
				::WaitForSingleObject(renderEvent, INFINITE);
				printf("%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
				fprintf(file, "%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);

			}
			fprintf(file, "bunny_%s\n", sahMode);
			for (float f = 0.45f; f<1.05f; f+=0.05f)
			{
				system->SahFactor = f;
				InitScene2();
				StartRender();
				::WaitForSingleObject(renderEvent, INFINITE);
				printf("%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
				fprintf(file, "%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);

			}
			fprintf(file, "sponge_%s\n", sahMode);
			for (float f = 0.45f; f<1.05f; f+=0.05f)
			{
				system->SahFactor = f;
				InitScene3();
				StartRender();
				::WaitForSingleObject(renderEvent, INFINITE);
				printf("%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);
				fprintf(file, "%f\t%lf\t%lf\n", f, (system->BvhProfiler.sumNodes/(double)system->BvhProfiler.rays), system->BvhProfiler.sumTri/(double)system->BvhProfiler.rays);

			}
			*/
		}
		fclose(file);
		CloseHandle(renderEvent);
	}

public:
	MainForm()
	{
		this->SetMaximizeBox(false);
		this->SetText(L"RayTracePro");
		this->SetBorder(GxWinGUI::fbSingle);
		this->SetClientWidth(WindowWidth);
		this->SetClientHeight(WindowHeight);
		this->OnKeyPressed.Bind(this, &MainForm::MainForm_KeyPressed);
		this->OnPaint.Bind(this, &MainForm::MainForm_Paint);
		this->OnMouseDown.Bind(this, &MainForm::MainForm_MouseDown);
		this->OnMouseMove.Bind(this, &MainForm::MainForm_MouseMove);
		this->OnMouseUp.Bind(this, &MainForm::MainForm_MouseUp);
		InitRenderer();
		onAdjustView = false;
	}
	~MainForm()
	{
		scene = 0;
		renderer = 0;
		buffer = 0;
		scheduler = 0;
		FreeRayTraceSystem(system);
	}
};

int __stdcall _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) 
{
	Application::Init(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
	MainForm * form = new MainForm();
	Application::Run(form);
	Application::Dispose();
	return 0;
}

int main()
{
	//TestPhotons();
	//TestRandom();
	printf("press 0 to start benchmark\n");
	return _tWinMain((HINSTANCE)GetModuleHandle(NULL), NULL, GetCommandLine(), 0);
}
